
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package co.edu.unal.bioingenium.kbmed.knowledge.ontology.graph;

import co.edu.unal.bioingenium.kbmed.knowledge.ontology.OntologyMetaData;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.dao.KnowledgeSourceDAO;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.dao.KnowledgeSourceDAOFactory;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.graph.vo.Edge;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.graph.vo.Hierarchy;
import co.edu.unal.bioingenium.kbmed.knowledge.ontology.graph.vo.Vertex;
import edu.uci.ics.jung.algorithms.shortestpath.DijkstraDistance;
import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath;
import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Alejandro
 */
public class GraphManager {

    //TODO ligthweight memory version of this class
    private static GraphManager graphManagerInstance;

    /**
     * 
     * @param ontologyMetaData
     * @return
     */
    public static GraphManager getInstance(OntologyMetaData ontologyMetaData) {
        if (graphManagerInstance == null) {
            graphManagerInstance = new GraphManager(ontologyMetaData);
            return graphManagerInstance;
        }
        return graphManagerInstance;
    }
    private Map<String, Hierarchy> hierarchyMap;
    private OntologyMetaData ontologyMetaData;
    private KnowledgeSourceDAO knowledgeSourceDAO;

    private GraphManager() {
    }

    /**
     * 
     * @param ontologyMetaData
     */
    private GraphManager(OntologyMetaData ontologyMetaData) {
        hierarchyMap = new HashMap<String, Hierarchy>();
        this.ontologyMetaData = ontologyMetaData;
        this.knowledgeSourceDAO = KnowledgeSourceDAOFactory.getKnowledgeSourceDAO(ontologyMetaData);
    }

    private Hierarchy getTestHierarchy(String testCategory) {
        String topConcept = "1";
        Hierarchy hierarchy = new Hierarchy(testCategory);
        hierarchy.setTopConceptId(topConcept);
        Graph<Vertex, Edge> undirectedGraph = new UndirectedSparseGraph<Vertex, Edge>();
        Graph<Vertex, Edge> directedGraph = new DirectedSparseMultigraph<Vertex, Edge>();
        Map<String, Vertex> vertexMap = new HashMap<String, Vertex>();
        for (int i = 1; i < 8; i++) {
            vertexMap.put(String.valueOf(i), new Vertex(String.valueOf(i)));
        }
        //Adding all vertex to graph
        for (String vertexId : vertexMap.keySet()) {
            undirectedGraph.addVertex(vertexMap.get(vertexId));
            directedGraph.addVertex(vertexMap.get(vertexId));
        }
        //Adding all edges to graph

        Vertex v1, v2;
        //
        v1 = vertexMap.get("6");
        v2 = vertexMap.get("4");
        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
        //
        v1 = vertexMap.get("4");
        v2 = vertexMap.get("2");
        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
        //
        v1 = vertexMap.get("4");
        v2 = vertexMap.get("3");
        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
        //
        v1 = vertexMap.get("2");
        v2 = vertexMap.get("1");
        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
        //
        v1 = vertexMap.get("3");
        v2 = vertexMap.get("1");
        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
        //
        v1 = vertexMap.get("7");
        v2 = vertexMap.get("5");
        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
        //
        v1 = vertexMap.get("5");
        v2 = vertexMap.get("3");
        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);


        //Setting graphs in the hierarchy
        hierarchy.setDirectedHierarchyGraph(directedGraph);
        hierarchy.setUndirectedHierarchyGraph(undirectedGraph);
        //
        hierarchy.setDepthMap(getDepthMap(undirectedGraph, vertexMap.get(topConcept)));
        hierarchy.setVertexMap(vertexMap);
        int temp, maxDepth = 0;
        for (Vertex vertex : hierarchy.getDepthMap().keySet()) {
            temp = hierarchy.getDepthMap().get(vertex).intValue();
            if (temp > maxDepth) {
                maxDepth = temp;
            }
        }
        hierarchy.setHierachyDepth(maxDepth);
        hierarchyMap.put(testCategory, hierarchy);
        return hierarchy;

    }

    /**
     * 
     * @param category
     */
    public void initHierarchy(String category) {
        getHierarchy(category);
    }

    /**
     * 
     * @param category
     * @return
     */
    public Hierarchy getHierarchy(String category) {
        if (hierarchyMap.containsKey(category)) {
            return hierarchyMap.get(category);
        } else {
            if (category.equalsIgnoreCase("test")) {
                return getTestHierarchy(category);
            }
            Logger.getLogger(GraphManager.class.getName()).log(Level.INFO, "Building the hierarchy object for category \"{0}\"", category);
            Hierarchy hierarchy = new Hierarchy(category);
            hierarchy.setTopConceptId(ontologyMetaData.getTopConceptId());
            Graph<Vertex, Edge> undirectedGraph = new UndirectedSparseGraph<Vertex, Edge>();
            Graph<Vertex, Edge> directedGraph = new DirectedSparseMultigraph<Vertex, Edge>();
            Map<String, Vertex> vertexMap = getVertexMap(category);
            //Adding all vertex to graph
            for (String vertexId : vertexMap.keySet()) {
                undirectedGraph.addVertex(vertexMap.get(vertexId));
                directedGraph.addVertex(vertexMap.get(vertexId));
            }
            //Adding all edges to graph
            Vertex v1, v2;
            Set<String> relatedConceptIds;
            for (String concept1Id : vertexMap.keySet()) {
                relatedConceptIds = knowledgeSourceDAO.getRelatedConcepts_2TO1(concept1Id, ontologyMetaData.getISARelationcship());
                v1 = vertexMap.get(concept1Id);
                for (String concept2Id : relatedConceptIds) {
                    if (vertexMap.keySet().contains(concept2Id)) {
                        v2 = vertexMap.get(concept2Id);
                        undirectedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.UNDIRECTED);
                        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
                    }
                }
            }
            //Setting graphs in the hierarchy
            hierarchy.setDirectedHierarchyGraph(directedGraph);
            hierarchy.setUndirectedHierarchyGraph(undirectedGraph);
            //
            hierarchy.setDepthMap(getDepthMap(undirectedGraph, vertexMap.get(ontologyMetaData.getTopConceptId())));
            hierarchy.setVertexMap(vertexMap);
            hierarchy.setLeavesSet(getLeavesSet(directedGraph));
            //calculate maxDepth
            int temp, maxDepth = 0;
            for (Vertex vertex : hierarchy.getDepthMap().keySet()) {
                temp = hierarchy.getDepthMap().get(vertex).intValue();
                if (temp > maxDepth) {
                    maxDepth = temp;
                }
            }
            hierarchy.setHierachyDepth(maxDepth);
            hierarchy.setTotalConcepts(vertexMap.size());
            hierarchyMap.put(category, hierarchy);
            Logger.getLogger(GraphManager.class.getName()).log(Level.INFO, "Building the hierarchy object for category \"{0}\" sucess. Total vertex {1} Total edges {2}", new Object[]{category, hierarchy.getDirectedHierarchyGraph().getVertices().size(), hierarchy.getDirectedHierarchyGraph().getEdges().size()});
            return hierarchy;
        }
    }
    
    /**
     * 
     * @param category
     * @return
     */
    public Hierarchy getLightHierarchy(String category) {
        if (hierarchyMap.containsKey(category)) {
            return hierarchyMap.get(category);
        } else {
            Logger.getLogger(GraphManager.class.getName()).log(Level.INFO, "Building the light hierarchy object for category \"{0}\"", category);
            Hierarchy hierarchy = new Hierarchy(category);
            hierarchy.setTopConceptId(ontologyMetaData.getTopConceptId());
            Graph<Vertex, Edge> directedGraph = new DirectedSparseMultigraph<Vertex, Edge>();
            Map<String, Vertex> vertexMap = getVertexMap(category);
            //Adding all vertex to graph
            for (String vertexId : vertexMap.keySet()) {
                directedGraph.addVertex(vertexMap.get(vertexId));
            }
            //Adding all edges to graph
            Vertex v1, v2;
            Set<String> relatedConceptIds;
            for (String concept1Id : vertexMap.keySet()) {
                relatedConceptIds = knowledgeSourceDAO.getRelatedConcepts_2TO1(concept1Id, ontologyMetaData.getISARelationcship());
                v1 = vertexMap.get(concept1Id);
                for (String concept2Id : relatedConceptIds) {
                    if (vertexMap.keySet().contains(concept2Id)) {
                        v2 = vertexMap.get(concept2Id);
                        directedGraph.addEdge(new Edge(v1, v2), v1, v2, EdgeType.DIRECTED);
                    }
                }
            }
            //Setting graphs in the hierarchy
            hierarchy.setDirectedHierarchyGraph(directedGraph);
            hierarchy.setVertexMap(vertexMap);
            hierarchy.setTotalConcepts(vertexMap.size());
            hierarchyMap.put(category, hierarchy);
            Logger.getLogger(GraphManager.class.getName()).log(Level.INFO, "Building the light hierarchy object for category \"{0}\" sucess. Total vertex {1} Total edges {2}", new Object[]{category, hierarchy.getDirectedHierarchyGraph().getVertices().size(), hierarchy.getDirectedHierarchyGraph().getEdges().size()});
            return hierarchy;
        }
    }

    /**
     * 
     * @param category
     * @return
     */
    public Graph<Vertex, Edge> getUndirectedHierarchyGraph(String category) {
        Graph<Vertex, Edge> undirectedGraph = new UndirectedSparseGraph<Vertex, Edge>();
        Map<String, Vertex> vertexMap = getVertexMap(category);
        //Adding all vertex to graph
        for (String vertexId : vertexMap.keySet()) {
            undirectedGraph.addVertex(vertexMap.get(vertexId));
        }
        //Adding all edges to graph
        Vertex c1, c2;
        Set<String> relatedConceptIds;
        for (String concept1Id : vertexMap.keySet()) {
            relatedConceptIds = knowledgeSourceDAO.getRelatedConcepts_2TO1(concept1Id, ontologyMetaData.getISARelationcship());
            c1 = vertexMap.get(concept1Id);
            for (String concept2Id : relatedConceptIds) {
                if (vertexMap.keySet().contains(concept2Id)) {
                    c2 = vertexMap.get(concept2Id);
                    undirectedGraph.addEdge(new Edge(c1, c2), c1, c2, EdgeType.UNDIRECTED);
                }
            }
        }
        return undirectedGraph;

    }

    /**
     * 
     * @param category
     * @return
     */
    public Graph<Vertex, Edge> getDirectedHierarchyGraph(String category) {
        Set<String> activeConceptIds = knowledgeSourceDAO.getActiveConceptsIds(category);
        Graph<Vertex, Edge> directedGraph = new DirectedSparseMultigraph<Vertex, Edge>();
        Map<String, Vertex> vertexMap = getVertexMap(category);
        //Adding all vertex to graph
        for (String vertexId : vertexMap.keySet()) {
            directedGraph.addVertex(vertexMap.get(vertexId));
        }
        //Adding all edges to graph
        Vertex c1, c2;
        Set<String> relatedConceptIds;
        for (String concept1Id : activeConceptIds) {
            relatedConceptIds = knowledgeSourceDAO.getRelatedConcepts_2TO1(concept1Id, ontologyMetaData.getISARelationcship());
            c1 = vertexMap.get(concept1Id);
            for (String concept2Id : relatedConceptIds) {
                if (activeConceptIds.contains(concept2Id)) {
                    c2 = vertexMap.get(concept2Id);
                    directedGraph.addEdge(new Edge(c1, c2), c1, c2, EdgeType.DIRECTED);
                }
            }
        }
        return directedGraph;
    }

    private Map<String, Vertex> getVertexMap(String category) {
        Set<String> conceptIds = knowledgeSourceDAO.getActiveConceptsIds(category);
        HashMap<String, Vertex> vertexMap = new HashMap<String, Vertex>();
        //Add top concept
        Vertex topVertex = new Vertex(ontologyMetaData.getTopConceptId());
        vertexMap.put(ontologyMetaData.getTopConceptId(), topVertex);
        //Adding all concepts
        Vertex vertex;
        for (String conceptId : conceptIds) {
            vertex = new Vertex(conceptId);
            vertexMap.put(conceptId, vertex);
        }
        return vertexMap;
    }

    /**
     * 
     * @param hierarchyGraph
     * @param topLevel
     * @return
     */
    public Map<Vertex, Number> getDepthMap(Graph<Vertex, Edge> hierarchyGraph, Vertex topLevel) {
        DijkstraDistance<Vertex, Edge> dijkstraDistance = new DijkstraDistance<Vertex, Edge>(hierarchyGraph);
        hierarchyGraph.getVertices().contains(topLevel);
        Map<Vertex, Number> distanceMap = dijkstraDistance.getDistanceMap(topLevel);
        return distanceMap;
    }

    /**
     * 
     * @param hierarchyGraph
     * @return
     */
    private Set<String> getLeavesSet(Graph<Vertex, Edge> hierarchyGraph) {
        Set<String> leavesSet = new HashSet<String>();
        Collection<Vertex> vertexCollection = hierarchyGraph.getVertices();
        for (Vertex vertex : vertexCollection) {
            if (hierarchyGraph.inDegree(vertex) == 0) {
                leavesSet.add(vertex.getConceptId());
            }
        }
        return leavesSet;
    }

    /**
     * 
     * @param category
     * @param conceptId
     * @return
     */
    public int getInDegree(String category, String conceptId) {
        return getHierarchy(category).getDirectedHierarchyGraph().inDegree(getHierarchy(category).getVertexMap().get(conceptId));
    }

    /**
     * 
     * @param category
     * @param conceptId
     * @return
     */
    public int getOutDegree(String category, String conceptId) {
        return getHierarchy(category).getDirectedHierarchyGraph().outDegree(getHierarchy(category).getVertexMap().get(conceptId));
    }

    /**
     * 
     * @param concept1
     * @param concept2
     * @param category
     * @return
     */
    public String getLeastCommonSubsummer(String concept1, String concept2, String category) {
        Graph<Vertex, Edge> hierarchyGraph = getHierarchy(category).getDirectedHierarchyGraph();
        DijkstraShortestPath<Vertex, Edge> shortestPath = new DijkstraShortestPath<Vertex, Edge>(hierarchyGraph);
        Vertex root = getHierarchy(category).getVertexMap().get(ontologyMetaData.getTopConceptId());
        List<Edge> shortestPathConcept1 = shortestPath.getPath(getHierarchy(category).getVertexMap().get(concept1), root);
        List<Edge> shortestPathConcept2 = shortestPath.getPath(getHierarchy(category).getVertexMap().get(concept2), root);
        List<Edge> shortEdgeList;
        List<Edge> largeEdgeList;
        if (shortestPathConcept1.size() > shortestPathConcept2.size()) {
            shortEdgeList = shortestPathConcept1;
            largeEdgeList = shortestPathConcept2;
        } else {
            shortEdgeList = shortestPathConcept2;
            largeEdgeList = shortestPathConcept1;
        }
        String leastCommonSubsumerId = "";
        for (Edge edge : shortEdgeList) {
            if (largeEdgeList.contains(edge)) {
                leastCommonSubsumerId = edge.getSource().getConceptId();
                break;
            }
        }
        return leastCommonSubsumerId;
    }

    /**
     * 
     * @param concept
     * @param category
     * @return
     */
    public List<Edge> getPathToRoot(String concept, String category) {
        Graph<Vertex, Edge> hierarchyGraph = getHierarchy(category).getDirectedHierarchyGraph();
        DijkstraShortestPath<Vertex, Edge> shortestPath = new DijkstraShortestPath<Vertex, Edge>(hierarchyGraph);
        Vertex source = getHierarchy(category).getVertexMap().get(concept);
        Vertex root = getHierarchy(category).getVertexMap().get(ontologyMetaData.getTopConceptId());
        return shortestPath.getPath(source, root);
    }

    /**
     * 
     * @param concept1
     * @param concept2 
     * @param category
     * @return
     */
    public List<Edge> getArtificialShortestPath(String concept1, String concept2, String category) {
        Graph<Vertex, Edge> hierarchyGraph = getHierarchy(category).getUndirectedHierarchyGraph();
        DijkstraShortestPath<Vertex, Edge> shortestPath = new DijkstraShortestPath<Vertex, Edge>(hierarchyGraph);
        Vertex source = getHierarchy(category).getVertexMap().get(concept1);
        Vertex target = getHierarchy(category).getVertexMap().get(concept2);
        return shortestPath.getPath(source, target);
    }

    /**
     * 
     * @param concept1
     * @param concept2 
     * @param category
     * @return
     */
    public List<Edge> getShortestPath(String concept1, String concept2, String category) {
        Graph<Vertex, Edge> hierarchyGraph = getHierarchy(category).getDirectedHierarchyGraph();
        DijkstraShortestPath<Vertex, Edge> shortestPath = new DijkstraShortestPath<Vertex, Edge>(hierarchyGraph);
        Vertex source = getHierarchy(category).getVertexMap().get(concept1);
        Vertex target = getHierarchy(category).getVertexMap().get(concept2);
        return shortestPath.getPath(source, target);
    }

    /**
     * 
     * @param conceptId
     * @param category
     * @return
     */
    public Set<String> getDirectParents(String category, String conceptId) {
        Set<String> parents = new HashSet<String>();
        Graph<Vertex, Edge> graph = getHierarchy(category).getDirectedHierarchyGraph();
        Collection<Vertex> successors = graph.getSuccessors(getHierarchy(category).getVertexMap().get(conceptId));
        for (Vertex vertex : successors) {
            parents.add(vertex.getConceptId());
        }
        return parents;
    }

    /**
     * 
     * @param conceptId
     * @param category
     * @return
     */
    public Set<String> getDirectChildrens(String category, String conceptId) {
        Set<String> childrens = new HashSet<String>();
        Graph<Vertex, Edge> graph = getHierarchy(category).getDirectedHierarchyGraph();
        Collection<Vertex> successors = graph.getPredecessors(getHierarchy(category).getVertexMap().get(conceptId));
        for (Vertex vertex : successors) {
            childrens.add(vertex.getConceptId());
        }
        return childrens;
    }

    /**
     * 
     * @param conceptId
     * @param category
     * @return
     */
    public Set<String> getDirectSiblings(String category, String conceptId) {
        Set<String> parents = getDirectParents(category, conceptId);
        Set<String> siblings = new HashSet<String>();
        for (String parent : parents) {
            siblings.addAll(getDirectChildrens(category, parent));
        }
        siblings.remove(conceptId);
        return siblings;
    }

    /**
     * 
     * @param conceptId
     * @param category
     * @return
     */
    public Set<String> getNeighbors(String category, String conceptId) {
        Set<String> neighborsIds = new HashSet<String>();
        Graph<Vertex, Edge> graph = getHierarchy(category).getDirectedHierarchyGraph();
        Collection<Vertex> neighbors = graph.getNeighbors(getHierarchy(category).getVertexMap().get(conceptId));
        for (Vertex vertex : neighbors) {
            neighborsIds.add(vertex.getConceptId());
        }
        return neighborsIds;
    }

    /**
     * 
     * @param conceptId
     * @param category
     * @return
     */
    public Map<Vertex, Number> getDijkstraDistanceMap(String conceptId, String category) {
        Graph<Vertex, Edge> graph = getHierarchy(category).getUndirectedHierarchyGraph();
        DijkstraDistance<Vertex, Edge> dijkstraDistance = new DijkstraDistance<Vertex, Edge>(graph);
        return dijkstraDistance.getDistanceMap(getHierarchy(category).getVertexMap().get(conceptId));
    }

    /**
     * 
     */
    public void clearAll() {
        hierarchyMap.clear();
    }
}
